'''
@author: nick mueller
'''

import os
import util
import Image
import Communication
import exchange_pb2
import shutil


_tlc = None

def getTLC():
    '''
    returns the instance for the camera
    '''
    global _tlc
    if(_tlc == None):
        _tlc = TLC("tlc_")
    
    return _tlc

class TLC:
    '''
    The TLC is the model for the camera and the processing of images and movies.
    It is responsible for the whole session management.
    '''
    
    META_FILE = "meta.tlc"
    TEST_IMG = "testImg.jpg"
    MOVIE_FILE = "movie.h264"
    RENDER_FILE = "movie.avi"
    THUMB_FILE = "thumb.jpg"
    
    def __init__(self, folderPrefix):
        # params for single image capture
        self.time = int(1000) #/2)
        self.maxTime = int(60000) #/2)
        self.tmpNumOfPics = 10

        # prefix for each capture folder
        self.folderPrefix = folderPrefix

        # process handle for exec subprocess
        self.process = None
        
        # value for rendering
        self.fps = 24
        
        self.folder = None
        self.inProcess = False

    def getPrefix(self):
        ''' returns the prefix for the session-directories '''
        return self.folderPrefix
    
    def createThumb(self, session):
        self.inProcess = True
        peo = util.ParallelExecOperation('raspistill -w 600 -h 337 -o ' + str('./' + session + TLC.THUMB_FILE))
        peo.setAfterFunc(self.__setNotInProcess)
        peo.start()
        peo.join()
    

    def getSessions(self):
        ''' 
        returns a list of "Session"-Class objects for all existing sessions.
        If no session exists, the list is empty
        '''
        dirs = os.listdir("./")
        
        toDel = []
        for d in dirs:
            if not(os.path.isdir(d) and d.startswith(self.folderPrefix)):
                toDel.append(d)
                
        for d in toDel:
            dirs.remove(d)
        
        sessions = []
        for d in dirs:
            sessions.append(self.getSession(d))
        
        return sessions
    
    def getSession(self, sessionDir):
        '''
        returns a "Session"-class object for requested session directory name.
        If the session does not exist, it returns None.
        '''
        if(sessionDir == None or sessionDir == ''):
            return None
        
        path = os.path.join('./', sessionDir)
        if os.path.isdir(path):
            session = Session()
            session.dirName = sessionDir
            
            try:
                f = open(os.path.join(path, TLC.META_FILE), "r+")
                session.createDate = f.readline()
                f.close()
            except Exception:
                session.createDate = ""

            try:
                session.thumb = self.__getThumb(sessionDir)
                if session.thumb == None: session.thumb = ""
                
            except Exception:
                session.thumb = ""
                
            try:
                session.numOfFiles = len(os.listdir(path)) - 1   
            except Exception:
                session.numOfFiles = 0       

            return session
        
        return None

    def deleteSession(self, session):
        if not(self.__isSession__(session)):
            raise Exception("session is not available")
        
        if not(session.endswith('/')): session += '/'
        
        shutil.rmtree('./'+session)

    def isInProcess(self):
        ''' returns the process state of the camera as a boolean '''
        return self.inProcess

    def __setNotInProcess(self):
        self.inProcess = False

    def setFPS(self, fps):
        ''' 
        sets the frame per second value for the movie rendering. 
        The default value is 20. The fps argument must greater than 0, 
        otherwise an exception will raise.
        '''
        if(fps <= 0): raise Exception("fps must greater than 0")
        self.fps = fps

    def setTimePerPic(self, timePerPic):
        '''
        is the time which the capture waits after each image capture
        timePerPic > 0, in seconds
        '''
        if(timePerPic <= 0): raise Exception("timePerPic must greater than 0")
        
        # after a long period of testing out the raspistill tool, I get the
        # right configuration by dividing the time per pic time and the whole
        # record time by two. 
        # prepare time: needed in millisecs and half-time
        self.time = int((timePerPic * 1000)) #/ 2)

    def setCaptureTime(self, timeInSecs):
        '''
        sets the whole capture time for a session recording. The timeInSecs
        argument must greater than 0, otherwise an exception will raise.
        '''
        if(timeInSecs <= 0): raise Exception("timeInSecs must greater than 0")
        self.maxTime = int((timeInSecs * 1000)) # / 2)

    def createSession(self, timestamp):
        '''
        creates a new session directory and the needed meta-file.
        The timestamp argument is a unix timestamp since 1.1.1970.
        
        This method returns a string with the created directory name.
        '''
        # get all elements of the current directory
        dirs = os.listdir("./")
        maxNum = 1000
        
        # iterate for all directories and find the biggest number
        for d in dirs:
            curNum = 0
            if os.path.isdir(d) and d.startswith(self.folderPrefix) :
                curNum = int(d[len(self.folderPrefix):])
            
            if curNum > maxNum:
                maxNum = curNum
        
        # increment for new directory
        maxNum += 1
        folder = self.folderPrefix + str(maxNum) + "/"
                
        # create folder
        os.makedirs(folder)
        
        # create meta file
        f = open(folder + TLC.META_FILE, 'w+')
        
        if timestamp == None or timestamp < 0 or timestamp == "": timestamp = 0
        f.write(str(timestamp))
        
        return folder

    def createTestImage(self):
        '''
        will start the camera for taking a full hd image. Until the camera is in
        process state. The method returns after the shooting (it takes 1-2 seconds).
        '''
        self.inProcess = True
        
        peo = util.ParallelExecOperation('raspistill -w 1920 -h 1080 -o ' + str(TLC.TEST_IMG))
        peo.setAfterFunc(self.__setNotInProcess)
        peo.start()
        peo.join()
        
    def __notifyAfterLongAction(self, message):
        self.__setNotInProcess()
        res = exchange_pb2.Response()
        res.type = res.STATUS
        res.status = message
        Communication.getCommunication().send(util.getDelimitMessage(res))

    def startMovie(self, session, durationInSecs):
        '''
        starts a movie record in full hd for a time (durationInSecs). For the movie will be
        created a new session directory.
        
        The method returns before the movie is finished. The movie record is in another thread.
        '''
        
        if not(self.__isSession__(session)):
            raise Exception("session is not available")
        
        if not(session.endswith('/')): session += '/'
        
        peo = util.ParallelExecOperation('raspivid -o ' + str(session) + TLC.RENDER_FILE + ' -t ' + str(durationInSecs * 1000))
        peo.setAfterFunc(self.__notifyAfterLongAction, "Capture finished ("+session+")")
        peo.start()
        
    def startCapture(self, session):
        '''
        starts the single image capture for a session. Uses the configured capture settings
        @see: setCaptureTime() and setTimePerPic()
        
        The record command will be executed in a new thread. This is why the method returns
        before the record ends.
        '''
        
        if not(self.__isSession__(session)):
            raise Exception("session is not available")
        
        if not(session.endswith('/')): session += '/'
        
        self.createThumb(session)
        
        # make shots 
        self.inProcess = True

        # old command for take images with gphoto2 and f.e. an DLSR
        # 'gphoto2 --capture-image-and-download --filename "' + str( self.folder ) +'%05n.jpg" --force-overwrite -F '+str( self.number )+' -I '+str( waitInSecs ) )

        # make shots with raspistill
        peo = util.ParallelExecOperation('raspistill -w 1920 -h 1080 -t ' + str(self.maxTime) + ' -tl ' + str(self.time) + ' -o ' + str(session) + '%05d.jpg')
        peo.setAfterFunc(self.__notifyAfterLongAction, "Capture finished ("+session+")")
        peo.start()

    def startRender(self, session):
        '''
        starts the very slow rendering process (on raspberry) for the requested session.
        This process will be executed in a new thread. This is why the method returns before
        the process ends.
        '''
        if(not(self.__isSession__(session))):
            raise Exception("session is not available")
        
        # get the first image file
        img = None
        for f in os.listdir("./" + session):
            if(f.endswith(".jpg")):
                img = f
                break
        
        # get image resolution
        img = Image.open('./' + os.path.join(session, img))
        res = img.size
        
        ses = os.path.join('./', session)
        if not(ses.endswith('/')): ses += '/'
        
        self.inProcess = True
        # render movie
        # printExecer( 'ffmpeg -f image2 -i '+self.folder+'%05d.jpg -r 20 -vb 3000k -vcodec libx264 -s '+str(res[0])+'x'+str(res[1])+' '+self.folder+'movie.avi' )
        
        # alternative rendering command for mjpeg (faster, but required a lot of space)
        #peo = util.ParallelExecOperation('mencoder -nosound -ovc copy -oac copy -o ' + ses + TLC.RENDER_FILE + ' -mf w='+ str(res[0]) +':h='+ str(res[1]) +':fps=' + str(self.fps) + ':type=jpg mf://' + ses + '*.jpg')
        
        # Exec for rendering
        peo = util.ParallelExecOperation('mencoder -nosound -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=8000000 -vf scale=' + str(res[0]) + ':' + str(res[1]) + ' -o ' + ses + TLC.RENDER_FILE + ' -mf type=jpeg:fps=' + str(self.fps) + ' mf://' + ses + '*.jpg')
        peo.setAfterFunc(self.__notifyAfterLongAction, "Rendering finished ("+session+")")
        peo.start()

    def stop(self):
        ''' deprecated '''
        if not(self.process == None):
            self.process.terminate()
            util.getLogger().info("Stop")
            
    def __isSession__(self, session):
        if(session == None or session == ''):
            return False
        
        if(os.path.isdir('./' + session)):
            return True
        else:
            return False
        
    def __getThumb(self, session):
        '''
        returns a thumb image of the first image of the given session.
        If the session does not exist, a exception is raising.
        
        @return: Exchange.File object 
        '''
        
        if not(self.__isSession__(session)):
            raise Exception("session is not available")
        
        if not(session.endswith('/')): session += '/'
        
        if os.path.exists('./' + session + TLC.THUMB_FILE):
            f = open('./' + session + TLC.THUMB_FILE, 'rb')
            img = f.read()
            f.close()
            return img
        else:
            return None

class Session:
    '''
    The Session class represents a session with the informations of interest.
    '''
    
    def __init__(self):
        self.dirName = ""
        self.createDate = ""
        self.numOfFiles = 0
        self.thumb = ""
    
    def isRendered(self):
        directory = os.listdir(os.path.join('./', self.dirName)) 
        
        for d in directory:
            if( d == TLC.RENDER_FILE or d == TLC.MOVIE_FILE ):
                return True;
        
        return False;
